#include "Hardware_handling.h"


Hardware_handling::Hardware_handling(void)
{
	cashier=0;
	total_price=0;
	barToquan.clear();
}


Hardware_handling::~Hardware_handling(void)
{
}

Hardware_handling::Hardware_handling(char cmd_in[], size_t len_buf)
{
	string str(cmd_in);
	cmd = str;
}

void Hardware_handling::set_cmd(char cmd_in[], size_t len_buf)
{
	string str(cmd_in);
	cmd = str;
}

/*
	Main function to intepret the command string input
	Output the response string
*/
string Hardware_handling::Intepret()
{
	assert(cmd.length()>=1);
	// Take the control char
	char control=cmd[5]-'0';
	string cId_str=cmd.substr(1,CASHIER_LENGTH);
	int cId=boost::lexical_cast<int>(cId_str);
	string result="";
	switch(control)
	{
	case Code(Start):
		result=Start_command();
		break;
	case Code(Add_item):
		if (is_number(cId_str) && cId==cashier)
			result=Add_command();
		else
			result=Not_Strt_Yet(cId);
		break;
	case Code(End):
		if (is_number(cId_str) && cId==cashier)
			result=End_command();
		else
			result=Not_Strt_Yet(cId);
		break;
	case Code(Cancel):
		if (is_number(cId_str) && cId==cashier)
			result=Cancel_command();
		else
			result=Not_Strt_Yet(cId);
		break;
	case Code(Remove_item):
		if (is_number(cId_str) && cId==cashier)
			result=Remove_command();
		else
			result=Not_Strt_Yet(cId);
		break;
	}

	return result;
}

string Hardware_handling::Not_Strt_Yet(int cId)
{

	string result="1"+ boost::lexical_cast<string>(cId);
	char ackn[2];
	char end[2];
	end[0]=END_BYTE+'0';
	end[1]='\0';
	ackn[0]=NOT_START_YET+'0';	
	ackn[1]='\0';
	result=result+ackn + end;
	return result;

}

string Hardware_handling::Start_command()
{
	string cId_str=cmd.substr(1,CASHIER_LENGTH);
	int cId=boost::lexical_cast<int>(cId_str);
	int i;
	char ackn[2];
	char end[2];
	end[0]=END_BYTE+'0';
	end[1]='\0';
	// Check if cashier exists or there is currently no transaction
	if (barToquan.size()==0 && is_number(cId_str) && check_CashierId(cId))
	{
		ackn[0]=START_SUCCESS+'0';	
		barToquan.clear();
		cashier=cId;
		total_price=0;
	}
	else
	{
		ackn[0]=START_FAIL+'0';
	}
	ackn[1]='\0';
		string result="1";
	result+=boost::lexical_cast<string>(cashier);
	// Finalize response message
	result=result+ackn + end;
	return result;
}

string Hardware_handling::Add_command()
{
	string result="";
	if (cmd.length()>= POS_START+2+BARCODE_LENGTH)
	{
		result="1"+ boost::lexical_cast<string>(cashier);
		int start_pos=7;
		string barcode_str=cmd.substr(POS_START,BARCODE_LENGTH);
		string quantity_str="";
		quantity_str+=cmd[POS_START+1+BARCODE_LENGTH];
		if (isdigit(cmd[POS_START+2+BARCODE_LENGTH]))
			quantity_str+=cmd[POS_START+2+BARCODE_LENGTH];

		int barcode=boost::lexical_cast<int>(barcode_str);
		int quantity=boost::lexical_cast<int>(quantity_str);
		char ackn[2];
		char end[2];
		end[0]=END_BYTE+'0';
		end[1]='\0';
		Item *iFind =new Item();
		string price="";
		// Check barcode exits or not
		if (is_number(barcode_str) && check_Barcode(barcode,iFind))
		{
			// Check stock and put item to the list
			if (iFind->get_ItemLocalStock()>=quantity)
			{
				ackn[0]=Put_Item_to_Map(iFind,quantity)+'0';
				int  pricy=(iFind->get_ItemPrice()+0.001)*100;
				// update total price
				total_price+=pricy*quantity;
				price=boost::lexical_cast<string>(pricy);
				if (price.length()==3)
					price="0"+price;
			}
			else
				ackn[0]=STOCK_NE+'0';
		}
		else
			ackn[0]=BARCODE_NE+'0';

		ackn[1]='\0';

		// Finalize response message
		result=result+ ackn+ price + end;
		delete iFind;
	}

	return result;
}

string Hardware_handling::End_command()
{
	string barcod;
	string quantit;
	string result="1"+ boost::lexical_cast<string>(cashier);
	if (barToquan.size() )
	{
		// Get customer money
		int c_money=get_Customer_money();
		int change=c_money-total_price;
		
		// If enought money then transaction success
		if (change>=0)
		{
			string change_str=convert_Change(c_money-total_price);
			// If customer pay enough money
			if (change_str!= TOO_MUCH)
			{
				//Set up SQL CONNECTOR
				sql_Connector *contor=new sql_Connector();
				contor->start_Connect();
				contor->chooseDB(LOCAL_DB);
				vector<string> Item_transact;
				vector<vector<string>> toTransaction;
				// Iterator to iterate the map
				typedef std::map<int,int>::iterator it_type;
				for(it_type iterator = barToquan.begin(); iterator != barToquan.end(); iterator++) {

					barcod=boost::lexical_cast<string>(iterator->first);
					quantit=boost::lexical_cast<string>(iterator->second);

					// Put the Item in the list to vector 
					Item_transact.clear();
					Item_transact.push_back(barcod);
					Item_transact.push_back(quantit);
					Item_transact.push_back(boost::lexical_cast<string>(cashier));
					toTransaction.push_back(Item_transact);
				}

				char ackn[2];
				char end[2];
				end[0]=END_BYTE+'0';
				end[1]='\0';
				ackn[0]=END_SUCCESS+'0';
				ackn[1]='\0';
				// Finally update database
				contor->insert_new_Transaction_list(toTransaction);
				// Compute result
				result=result+ackn+ change_str + end;
				delete contor;
				barToquan.clear();
				cashier=0;
				total_price=0;
			}
			else
			{
				char ackn[2];
				char end[2];
				end[0]=END_BYTE+'0';
				end[1]='\0';
				ackn[0]=TOO_MUCH_MONEY+'0';
				ackn[1]='\0';
				result=result+ackn + end;
				// Notify the shop server
				string sms="Customer at cashier:"+ boost::lexical_cast<string>(cashier) + " is too rich!";
				QFuture<void> res = QtConcurrent::run(this, &Hardware_handling::sendSMS,sms);
			}
		}
		else
		{
			char ackn[2];
			char end[2];
			end[0]=END_BYTE+'0';
			end[1]='\0';
			ackn[0]=END_FAIL+'0';
			ackn[1]='\0';
			result=result+ackn + end;
		}
	}
	else
	{
		char ackn[2];
		char end[2];
		end[0]=END_BYTE+'0';
		end[1]='\0';
		ackn[0]=END_WARNING+'0';
		ackn[1]='\0';
		result=result+ackn  + end;
	}
	return result;
}

string Hardware_handling::Remove_command()
{
	string result="";
	if (cmd.length()>= POS_START+2+BARCODE_LENGTH)
	{
		result="1"+ boost::lexical_cast<string>(cashier);
		Item *iFind =new Item();
		string price="";
		int start_pos=7;
		string barcode_str=cmd.substr(POS_START,BARCODE_LENGTH);
		string quantity_str="";
		quantity_str+=cmd[POS_START+1+BARCODE_LENGTH];
		if (isdigit(cmd[POS_START+2+BARCODE_LENGTH]))
			quantity_str+=cmd[POS_START+2+BARCODE_LENGTH];

		int barcode=boost::lexical_cast<int>(barcode_str);
		int quantity=boost::lexical_cast<int>(quantity_str);
		char ackn[2];
		char end[2];
		end[0]=END_BYTE+'0';
		end[1]='\0';
		int cur_quantity;

		if ( barToquan.find(barcode) == barToquan.end() ) {
			// not found
			ackn[0]=RM_BARCODE_NE+'0';
			ackn[1]='\0';
		} else {
			// found
			cur_quantity=barToquan.find(barcode)->second;
			if (quantity<= cur_quantity)
			{
				// remove quantity <= quantity
				ackn[0]=REMOVE_SUCCESS+'0';
				ackn[1]='\0';
				check_Barcode(barcode,iFind);
				int  pricy=(iFind->get_ItemPrice()+0.001)*100;
				// reduce total_price
				total_price-=pricy*quantity;
				price=boost::lexical_cast<string>(pricy);
				if (price.length()==3)
					price="0"+price;
				if (cur_quantity==quantity)
				{
					barToquan.erase(barcode);
				}
				else
				{
					barToquan.find(barcode)->second=cur_quantity-quantity;
				}
			}
			else
			{
				// remove quantity > current quantity
				ackn[0]=RM_Q_LARGE+'0';
				ackn[1]='\0';
			}

		}
		// Finalize response message
		result=result+ ackn+ price + end;

		delete iFind;
	}
	return result;
}

string Hardware_handling::Cancel_command()
{
	string sms="Transaction Cancel in cashier: " + boost::lexical_cast<string>(cashier);
	QFuture<void> res = QtConcurrent::run(this, &Hardware_handling::sendSMS,sms);
	barToquan.clear();
	string result="1"+ boost::lexical_cast<string>(cashier);
	char ackn[2];
	char end[2];
	end[0]=END_BYTE+'0';
	end[1]='\0';
	ackn[0]=CANCEL_SUCCESS+'0';
	ackn[1]='\0';
	result=result+ackn +end;
	cashier=0;
	return result;
}

bool Hardware_handling::check_CashierId(int cashierId)
{
	bool result;
	sql_Connector *contor=new sql_Connector();
	contor->start_Connect();
	contor->chooseDB(LOCAL_DB);
	if (contor->search_cashier_from_cId(cashierId))
		result=true;
	else
		result=false;

	return result;
}

bool Hardware_handling::check_Barcode(int barcode,Item *iFind)
{
		bool result;
	sql_Connector *contor=new sql_Connector();
	contor->start_Connect();
	contor->chooseDB(LOCAL_DB);
	
	if (contor->search_from_barcode(barcode,iFind))
		result=true;
	else
		result=false;

	return result;
}

bool Hardware_handling::is_number(const std::string& s)
{
	return !s.empty() && std::find_if(s.begin(), 
        s.end(), [](char c) { return !isdigit(c); }) == s.end();
}

/*
	Return ADD_SUCCESS if everything is OK
	Return STOCK_NE	if quantity  is larger than current stock
*/
int Hardware_handling::Put_Item_to_Map(Item *iFind,int quantity)
{
	int result;
	int  barcode=iFind->get_ItemID();
	int curQuan;
	if (barToquan.find(barcode) != barToquan.end()) // Exist item
	{
		curQuan=barToquan.find(barcode)->second+quantity;
		if (curQuan<=iFind->get_ItemLocalStock())
		{
			barToquan.find(barcode)->second=curQuan;
			result=ADD_SUCCESS;
		}
		else
		{
			result=STOCK_NE;
		}
	}
	else
	{
		barToquan.insert(std::pair<int,int>(barcode,quantity));
		result=ADD_SUCCESS;
	}

	return result;
}

string Hardware_handling::to_Price_display(int pId)
{
	string line0="";
	string line1="";
	string result="";
	sql_Connector *contor=new sql_Connector();
	contor->start_Connect();
	contor->chooseDB(LOCAL_DB);
	// Compute header:
	string p="0";
	string header_conv=convertDec(pId);

	line0=header_conv;
	// Search barcode from pId
	string barcode=contor->search_from_pId(boost::lexical_cast<string>(pId));
	if (barcode!="")
	{
		// get the Item info from barcode
		Item *tmp=new Item();
		contor->search_from_barcode(boost::lexical_cast<int>(barcode),tmp);
		// Compute line0: item name
		line0+=tmp->get_ItemName().substr(0,16);
		while (line0.length()< header_conv.length() + 16)
			line0+=" ";
		// Compute line1: price + stock high or low
		string price=tmp->get_ItemPrice_toStr();
		if (tmp->get_ItemPrice() < 10.0)
			price="0" + price;
		if (price.length()<5)
			price+="0";
		line1="$" + price + "  stock: ";
		if (tmp->get_ItemLocalStock()<=tmp->get_ItemMinStock())
			line1+="L";
		else
			line1+="H";
		char end[2];
		end[0]=END_BYTE+'0';
		end[1]='\0';
		line1+=end;
		delete contor;
		result=line0+line1;
	}

	return result;
}

string Hardware_handling::convertDec(int number)
{
	//string invert="";
	int z=4;
	string result="00000";
	char t;
	while (number)
	{
		t=number%10+ '0';
		number/=10;
		result[z]=t;
		z--;
	}
	
	return result;
}

void Hardware_handling::sendSMS(string sms)
{
	    PyObject *pName, *pModule, *pFunc;
    PyObject *pArgs, *pValue;
	//string SMS_send="Counter cancel!";
    Py_Initialize();
    pName = PyUnicode_FromString("sendsms");
    pModule = PyImport_Import(pName);
    Py_DECREF(pName);

    if (pModule != NULL) {
        pFunc = PyObject_GetAttrString(pModule, "send_sms");

        if (pFunc && PyCallable_Check(pFunc)) {
			// Have to use c_str
			pArgs = Py_BuildValue("(s)",sms.c_str());
            pValue = PyObject_CallObject(pFunc, pArgs);
            Py_DECREF(pArgs);

            if (pValue != NULL) {
                printf("Result of send_sms: %ld\n", PyLong_AsLong(pValue));
                Py_DECREF(pValue);
            }
            else {
                Py_DECREF(pFunc);
                Py_DECREF(pModule);
                PyErr_Print();
                fprintf(stderr,"Call failed\n");
            }
        }
        else {
            if (PyErr_Occurred())
                PyErr_Print();
            fprintf(stderr, "Cannot find function \n");
        }
        Py_XDECREF(pFunc);
        Py_DECREF(pModule);
    }
    else {
        PyErr_Print();
        fprintf(stderr, "Failed to load \n");
    }
    Py_Finalize();

}

int Hardware_handling::get_Customer_money()
{
	int result=0;
	int size= cmd.length();
	int i=0;

	for (i=POS_START;i<size;i++)
	{
		if (isdigit(cmd[i]))
			result=result*10 + (cmd[i]-'0');
		else if (cmd[i]=';')
			break;

	}
	return result;
}

string Hardware_handling::convert_Change(int change)
{
	string result=boost::lexical_cast<string>(change);
	if (result.length()>6)
		result=TOO_MUCH;
	else if (result.length()<6)
	{
		while (result.length()<6)
			result="0" + result;
	}
	return result;
}